package com.example.service.impl;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.entity.dto.AccountDetailsDto;
import com.example.entity.dto.AccountDto;
import com.example.entity.dto.AccountPrivacyDto;
import com.example.entity.vo.request.*;
import com.example.mapper.AccountDetailsMapper;
import com.example.mapper.AccountMapper;
import com.example.mapper.AccountPrivacyMapper;
import com.example.service.AccountService;
import com.example.utils.Const;
import com.example.utils.FlowUtils;
import jakarta.annotation.Resource;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;

import static com.example.utils.Const.VERIFY_EMAIL_DATA;
import static com.example.utils.Const.VERIFY_EMAIL_LIMIT;

@Service
public class AccountServiceImpl
        extends ServiceImpl<AccountMapper, AccountDto>
        implements AccountService{

    //信息队列
    @Resource
    private AmqpTemplate amqpTemplate;

    //redis
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    //限流工具
    @Resource
    private FlowUtils flowUtils;

    //密码加密
    @Resource
    private PasswordEncoder passwordEncoder;

    @Resource
    private AccountPrivacyMapper accountPrivacyMapper;

    @Resource
    private AccountDetailsMapper accountDetailsMapper;


    //自定义查询用户信息（接口在UserDetailsService中）
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        AccountDto account = this.findAccountByNameOrEmail(username);
        if(account == null){
            throw new UsernameNotFoundException("用户名or密码错误");
        }
        return User
//                有可能是邮箱？没懂
//                .withUsername(account.getUsername())
                .withUsername(username)
                .password(account.getPassword())
                .roles(account.getUsername())
                .build();
    }

    //查询用户信息
    public AccountDto findAccountByNameOrEmail(String text){
        return this.query()
                .eq("username",text)
                .or()
                .eq("email",text)
                .one();
    }

    //请求邮箱验证码
    @Override
    public String registerEmailVerifyCode(String type, String email, String ip) {
        //上锁
        synchronized (ip.intern()){
            if (!this.verifyLimit(ip)){
                return "请求频繁！";
            }
            Random random = new Random();
            //随机六位数验证码
            int code = 100000 + random.nextInt(900000);
            //用户验证消息放进消息队列
            Map<String, Object> map = Map.of("type",type,"email",email,"code",code);
            amqpTemplate.convertAndSend("mail",map);
            //生成的验证码存在redis中，三分钟有效
            stringRedisTemplate.opsForValue()
                    .set(VERIFY_EMAIL_DATA+email,String.valueOf(code),3, TimeUnit.MINUTES);
            return null;
        }
    }

    //邮箱注册
    @Override
    public String registerEmailAccount(EmailRegisterVo vo) {
        //1.检查验证码
        String code = vo.getCode();
        String rightCode = stringRedisTemplate.opsForValue().get(VERIFY_EMAIL_DATA+vo.getEmail());
        if(rightCode == null) {
            return "请先获取验证码";
        }
        if(!code.equals(rightCode)){
            return "验证码错误，请重新输入";
        }
        //2.检查邮箱
        String email = vo.getEmail();
        if (existAccountByEmail(email)){
            return "当前邮箱已注册";
        }
        //3.检查用户名
        String username = vo.getUsername();
        if (existAccountByUsername(username)){
            return "当前用户名已存在";
        }

        //4.创建用户
        String password = passwordEncoder.encode(vo.getPassword());
        AccountDto account = new AccountDto(null, vo.getUsername(),
                password, email, Const.ROLE_DEFAULT, null,new Date());
        if(!this.save(account)) {
            return "内部错误，注册失败";
        } else {
            this.deleteEmailVerifyCode(email);
            /*
             * 添加一些其他的默认用户信息，privacy和detail
             */
            //用户隐私
            accountPrivacyMapper.insert(new AccountPrivacyDto(account.getId()));
            //其他信息
            AccountDetailsDto accountDetailsDto = new AccountDetailsDto();
            accountDetailsDto.setId(account.getId());
            accountDetailsMapper.insert(accountDetailsDto);
            return null;
        }
    }

    //验证重置密码的邮箱验证码
    @Override
    public String resetCodeConfirm(ResetCodeConfirmVo vo) {
        //1.检查验证码
        String email = vo.getEmail();
        String code = vo.getCode();
//        String rightCode = stringRedisTemplate.opsForValue().get(VERIFY_EMAIL_DATA+email);
        String rightCode = this.getRightCodeFromRedis(email);
        if(rightCode == null) {
            return "请先获取验证码";
        }
        if(!code.equals(rightCode)){
            return "验证码错误，请重新输入";
        }
        return null;
    }

    //重置密码
    @Override
    public String resetAccountPassword(ResetAccountPasswordVo vo) {
        String confirm = this.resetCodeConfirm(vo);
        if(confirm != null){
            return confirm;
        }
        String email = vo.getEmail();
        String password = passwordEncoder.encode(vo.getPassword());
        //更新账户密码
        boolean updated = this.update()
                .eq("email", email)
                .set("password", password)
                .update();
        if (updated){
            //产出redis中的验证码
            this.deleteEmailVerifyCode(email);
            return null;
        }else {
            return "更新失败，请联系管理员";
        }
    }

    /**
     * 通过Id查找用户信息
     * @param id 用户Id
     * @return 账户信息
     */
    @Override
    public AccountDto findAccountById(int id) {
        return this.query().eq("id", id).one();
    }

    /**
     * 更新电子邮箱
     * @param id 当前用户Id
     * @param vo 邮箱和验证码
     * @return 结果
     */
    @Override
    public String modifyEmail(int id, ModifyEmailVo vo) {
        //取出邮箱和验证码
        String email = vo.getEmail();
        String code = vo.getCode();
        String rightCode = this.getRightCodeFromRedis(email);
        if(rightCode == null) return "请先获取验证码！";
        if(!code.equals(vo.getCode())) return "验证码错误，请重新输入";
        this.deleteEmailVerifyCode(email);
        AccountDto account = this.findAccountByNameOrEmail(email);
        if(account != null && account.getId() != id)
            return "该电子邮件已经被其他账号绑定，无法完成此操作！";
        this.update()
                .set("email", email)
                .eq("id", id)
                .update();
        return null;
    }

    /**
     * 修改密码
     * @param id 用户Id
     * @param vo 新旧密码
     * @return 结果
     */
    @Override
    public String changePassword(int id, ChangePasswordVo vo) {
        //检查原密码是否正确
        String password = this.query().eq("id", id).one().getPassword();
        if(!passwordEncoder.matches(vo.getPassword(), password))
            return "原密码错误，请重新输入！";
        //更新密码
        boolean success = this.update()
                .eq("id", id)
                .set("password", passwordEncoder.encode(vo.getNew_password()))
                .update();
        return success ? null : "未知错误，请联系管理员";
    }

    //检查是否需要限制发送邮件
    private boolean verifyLimit(String address){
        String key = VERIFY_EMAIL_LIMIT+address;
        //限制60s发送一次
        return flowUtils.limitOnceCheck(key,60);
    }

    //检查邮箱是否存在
    private boolean existAccountByEmail(String email){
        return this.baseMapper.exists(
                Wrappers.<AccountDto>query().eq("email",email)
        );
    }

    //检查用户名是否存在
    private boolean existAccountByUsername(String username){
        return this.baseMapper.exists(
                Wrappers.<AccountDto>query().eq("username",username)
        );
    }

    /**
     * 移除Redis中存储的邮件验证码
     * @param email 电邮
     */
    private void deleteEmailVerifyCode(String email){
        String key = Const.VERIFY_EMAIL_DATA + email;
        stringRedisTemplate.delete(key);
    }

    /**
     * 从redis中取出邮箱验证码
     * @param email 目标邮箱
     * @return 验证码
     */
    private String getRightCodeFromRedis(String email){
        return stringRedisTemplate.opsForValue().get(VERIFY_EMAIL_DATA+email);
    }
}
